#include "../stdafx.h"
#include "platwin.h"
#include "../util/util.h"


static bool onNT = true;




long Tick::GetTimeTick() {
	return ::GetTickCount();
}


namespace soy {


Point Point::FromLong( long lpoint ) {
	return Point( static_cast<short>( LOWORD( lpoint ) ), static_cast<short>( HIWORD( lpoint ) ) );
}
bool ListBoxX_Register();
bool ListBoxX_Unregister();

HINSTANCE GetInstance() {
	return ::GetModuleHandleW( NULL );
}


void* PointerFromWindow( HWND hWnd ) {
	return reinterpret_cast<void*>( ::GetWindowLongPtr( hWnd, 0 ) );
}
void SetWindowPointer( HWND hWnd, void* ptr ) {
	::SetWindowLongPtr( hWnd, 0, reinterpret_cast<LONG_PTR>( ptr ) );
}
void SetWindowID( HWND hWnd, int identifier ) {
	::SetWindowLongPtr( hWnd, GWLP_ID, identifier );
}



static CRITICAL_SECTION crPlatformLock;
static HCURSOR reverseArrowCursor = NULL;

void LockSection() {
	::EnterCriticalSection( &crPlatformLock );
}
void UnlockSection() {
	::LeaveCriticalSection( &crPlatformLock );
}

RECT Rect2RECT( Rect& r ) {
	RECT rect = {0};
	rect.left = r.left;
	rect.bottom = r.bottom;
	rect.right = r.right;
	rect.top = r.top;
	return rect;
}
Rect RECT2Rect( RECT r ) {
	Rect r2;
	r2.left = r.left;
	r2.right = r.right;
	r2.top = r.top;
	r2.bottom = r.bottom;
	return r2;
}

bool IsNT() {
	static int flag = 0;
	if( flag == 0 ) {
		OSVERSIONINFO osv = {sizeof( OSVERSIONINFO ), 0, 0, 0, 0, TEXT( "" )};
		::GetVersionEx( &osv );
		if( osv.dwPlatformId == VER_PLATFORM_WIN32_NT ) {
			flag = 1;
		} else {
			flag = -1;
		}
	}
	return flag > 0;
}




HFONT FormatAndMetrics::HFont() {
	LOGFONTW lf;
	memset( &lf, 0, sizeof( lf ) );
	if ( 0 == ::GetObjectW( hfont, sizeof( lf ), &lf ) ) {
		return 0;
	}
	return ::CreateFontIndirectW( &lf );
}

Palette::Palette() {
	used = 0;
	allowRealization = false;
	hpal = 0;
	size = 100;
	entries = new ColourPair[size];
}

Palette::~Palette() {
	Release();
	delete []entries;
	entries = 0;
}

void Palette::Release() {
	used = 0;
	if ( hpal ) {
		::DeleteObject( hpal );
	}
	hpal = 0;
	delete []entries;
	size = 100;
	entries = new ColourPair[size];
}

/**
 * This method either adds a colour to the list of wanted colours (want==true)
 * or retrieves the allocated colour back to the ColourPair.
 * This is one method to make it easier to keep the code for wanting and retrieving in sync.
 */
void Palette::WantFind( ColourPair& cp, bool want ) {
	if ( want ) {
		for ( int i = 0; i < used; i++ ) {
			if ( entries[i].desired == cp.desired ) {
				return;
			}
		}
		if ( used >= size ) {
			int sizeNew = size * 2;
			ColourPair* entriesNew = new ColourPair[sizeNew];
			for ( int j = 0; j < size; j++ ) {
				entriesNew[j] = entries[j];
			}
			delete []entries;
			entries = entriesNew;
			size = sizeNew;
		}
		entries[used].desired = cp.desired;
		entries[used].allocated.Set( cp.desired.AsLong() );
		used++;
	} else {
		for ( int i = 0; i < used; i++ ) {
			if ( entries[i].desired == cp.desired ) {
				cp.allocated = entries[i].allocated;
				return;
			}
		}
		cp.allocated.Set( cp.desired.AsLong() );
	}
}

void Palette::Allocate( Window& ) {
	if ( hpal ) {
		::DeleteObject( hpal );
	}
	hpal = 0;
	if ( allowRealization ) {
		char* pal = new char[sizeof( LOGPALETTE ) + ( used - 1 ) * sizeof( PALETTEENTRY )];
		LOGPALETTE* logpal = reinterpret_cast<LOGPALETTE*>( pal );
		logpal->palVersion = 0x300;
		logpal->palNumEntries = static_cast<WORD>( used );
		for ( int iPal = 0; iPal < used; iPal++ ) {
			Color desired = entries[iPal].desired;
			logpal->palPalEntry[iPal].peRed   = static_cast<BYTE>( desired.GetRed() );
			logpal->palPalEntry[iPal].peGreen = static_cast<BYTE>( desired.GetGreen() );
			logpal->palPalEntry[iPal].peBlue  = static_cast<BYTE>( desired.GetBlue() );
			entries[iPal].allocated.Set(
				PALETTERGB( desired.GetRed(), desired.GetGreen(), desired.GetBlue() ) );
			// PC_NOCOLLAPSE means exact colours allocated even when in background this means other windows
			// are less likely to get their colours and also flashes more when switching windows
			logpal->palPalEntry[iPal].peFlags = PC_NOCOLLAPSE;
			// 0 allows approximate colours when in background, yielding moe colours to other windows
			//logpal->palPalEntry[iPal].peFlags = 0;
		}
		hpal = ::CreatePalette( logpal );
		delete []pal;
	}
}

static BYTE Win32MapFontQuality( int extraFontFlag ) {
	switch ( extraFontFlag & SC_EFF_QUALITY_MASK ) {
	case SC_EFF_QUALITY_NON_ANTIALIASED:
		return NONANTIALIASED_QUALITY;
	case SC_EFF_QUALITY_ANTIALIASED:
		return ANTIALIASED_QUALITY;
	case SC_EFF_QUALITY_LCD_OPTIMIZED:
		return CLEARTYPE_QUALITY;
	default:
		return SC_EFF_QUALITY_DEFAULT;
	}
}
static void SetLogFont( LOGFONTA& lf, const char* faceName, int characterSet, int size, bool bold, bool italic, int extraFontFlag ) {
	memset( &lf, 0, sizeof( lf ) );
	// The negative is to allow for leading
	lf.lfHeight = -( abs( size ) );
	lf.lfWeight = bold ? FW_BOLD : FW_NORMAL;
	lf.lfItalic = static_cast<BYTE>( italic ? 1 : 0 );
	lf.lfCharSet = static_cast<BYTE>( characterSet );
	lf.lfQuality = Win32MapFontQuality( extraFontFlag );
	strncpy( lf.lfFaceName, faceName, sizeof( lf.lfFaceName ) );
}

/**
 * Create a hash from the parameters for a font to allow easy checking for identity.
 * If one font is the same as another, its hash will be the same, but if the hash is the
 * same then they may still be different.
 */
static int HashFont( const char* faceName, int characterSet, int size, bool bold, bool italic, int extraFontFlag ) {
	return
		size ^
		( characterSet << 10 ) ^
		( ( extraFontFlag & SC_EFF_QUALITY_MASK ) << 9 ) ^
		( bold ? 0x10000000 : 0 ) ^
		( italic ? 0x20000000 : 0 ) ^
		faceName[0];
}
class FontCached : Font {
	FontCached* next;
	int usage;
	LOGFONTA lf;
	int hash;
	FontCached( const char* faceName_, int characterSet_, int size_, bool bold_, bool italic_, int extraFontFlag_ );
	~FontCached() {}
	bool SameAs( const char* faceName_, int characterSet_, int size_, bool bold_, bool italic_, int extraFontFlag_ );
	virtual void Release();

	static FontCached* first;
public:
	static FontID FindOrCreate( const char* faceName_, int characterSet_, int size_, bool bold_, bool italic_, int extraFontFlag_ );
	static void ReleaseId( FontID fid_ );
};

FontCached* FontCached::first = 0;

FontCached::FontCached( const char* faceName_, int characterSet_, int size_, bool bold_, bool italic_, int extraFontFlag_ ) :
	next( 0 ), usage( 0 ), hash( 0 ) {
	SetLogFont( lf, faceName_, characterSet_, size_, bold_, italic_, extraFontFlag_ );
	hash = HashFont( faceName_, characterSet_, size_, bold_, italic_, extraFontFlag_ );
	fid = ::CreateFontIndirectA( &lf );
	usage = 1;
}

bool FontCached::SameAs( const char* faceName_, int characterSet_, int size_, bool bold_, bool italic_, int extraFontFlag_ ) {
	return
		( lf.lfHeight == -( abs( size_ ) ) ) &&
		( lf.lfWeight == ( bold_ ? FW_BOLD : FW_NORMAL ) ) &&
		( lf.lfItalic == static_cast<BYTE>( italic_ ? 1 : 0 ) ) &&
		( lf.lfCharSet == characterSet_ ) &&
		( lf.lfQuality == Win32MapFontQuality( extraFontFlag_ ) ) &&
		0 == strcmp( lf.lfFaceName, faceName_ );
}

void FontCached::Release() {
	if ( fid ) {
		::DeleteObject( fid );
	}
	fid = 0;
}

FontID FontCached::FindOrCreate( const char* faceName_, int characterSet_, int size_, bool bold_, bool italic_, int extraFontFlag_ ) {
	FontID ret = 0;
	::EnterCriticalSection( &crPlatformLock );
	int hashFind = HashFont( faceName_, characterSet_, size_, bold_, italic_, extraFontFlag_ );
	for ( FontCached* cur = first; cur; cur = cur->next ) {
		if ( ( cur->hash == hashFind ) &&
				cur->SameAs( faceName_, characterSet_, size_, bold_, italic_, extraFontFlag_ ) ) {
			cur->usage++;
			ret = cur->fid;
		}
	}
	if ( ret == 0 ) {
		FontCached* fc = new FontCached( faceName_, characterSet_, size_, bold_, italic_, extraFontFlag_ );
		if ( fc ) {
			fc->next = first;
			first = fc;
			ret = fc->fid;
		}
	}
	::LeaveCriticalSection( &crPlatformLock );
	return ret;
}

void FontCached::ReleaseId( FontID fid_ ) {
	::EnterCriticalSection( &crPlatformLock );
	FontCached** pcur = &first;
	for ( FontCached* cur = first; cur; cur = cur->next ) {
		if ( cur->fid == fid_ ) {
			cur->usage--;
			if ( cur->usage == 0 ) {
				*pcur = cur->next;
				cur->Release();
				cur->next = 0;
				delete cur;
			}
			break;
		}
		pcur = &cur->next;
	}
	::LeaveCriticalSection( &crPlatformLock );
}
#define FONTS_CACHED
Font::Font() {
	fid = 0;
}

Font::~Font() {
}



void Font::Create( const char* faceName, int characterSet, int size,
				   bool bold, bool italic, int extraFontFlag ) {
	Release();
#ifndef FONTS_CACHED
	LOGFONT lf;
	SetLogFont( lf, faceName, characterSet, size, bold, italic, extraFontFlag );
	fid = ::CreateFontIndirect( &lf );
#else
	if ( faceName ) {
		fid = FontCached::FindOrCreate( faceName, characterSet, size, bold, italic, extraFontFlag );
	}
#endif
}

void Font::Release() {
#ifndef FONTS_CACHED
	if ( fid ) {
		::DeleteObject( fid );
	}
#else
	if ( fid ) {
		FontCached::ReleaseId( fid );
	}
#endif
	fid = 0;
}


Menu::Menu() : mid( 0 ) {
}

void Menu::CreatePopUp() {
	Destroy();
	mid = ::CreatePopupMenu();
}

void Menu::Destroy() {
	if ( mid ) {
		::DestroyMenu( reinterpret_cast<HMENU>( mid ) );
	}
	mid = 0;
}

void Menu::Show( Point pt, Window& w ) {
	::TrackPopupMenu( reinterpret_cast<HMENU>( mid ),
					  0, pt.x - 4, pt.y, 0,
					  reinterpret_cast<HWND>( w.GetID() ), NULL );
	Destroy();
}

Color Platform::Chrome() {
	return ::GetSysColor( COLOR_3DFACE );
}

Color Platform::ChromeHighlight() {
	return ::GetSysColor( COLOR_3DHIGHLIGHT );
}

const char* Platform::DefaultFont() {
	return "Verdana";
}

int Platform::DefaultFontSize() {
	return 8;
}

unsigned int Platform::DoubleClickTime() {
	return ::GetDoubleClickTime();
}

bool Platform::MouseButtonBounce() {
	return false;
}

void Platform::DebugDisplay( const char* s ) {
	::OutputDebugStringA( s );
}

bool Platform::IsKeyDown( int key ) {
	return ( ::GetKeyState( key ) & 0x80000000 ) != 0;
}

long Platform::SendScintilla( WindowID w, unsigned int msg, unsigned long wParam, long lParam ) {
	return ::SendMessage( reinterpret_cast<HWND>( w ), msg, wParam, lParam );
}

long Platform::SendScintillaPointer( WindowID w, unsigned int msg, unsigned long wParam, void* lParam ) {
	return ::SendMessage( reinterpret_cast<HWND>( w ), msg, wParam,
						  reinterpret_cast<LPARAM>( lParam ) );
}

bool Platform::IsDBCSLeadByte( int codePage, char ch ) {
	return ::IsDBCSLeadByteEx( codePage, ch ) != 0;
}

int Platform::DBCSCharLength( int codePage, const char* s ) {
	return ( ::IsDBCSLeadByteEx( codePage, s[0] ) != 0 ) ? 2 : 1;
}

int Platform::DBCSCharMaxLength() {
	return 2;
}

// These are utility functions not really tied to a platform

int Platform::Minimum( int a, int b ) {
	if ( a < b ) {
		return a;
	} else {
		return b;
	}
}

int Platform::Maximum( int a, int b ) {
	if ( a > b ) {
		return a;
	} else {
		return b;
	}
}

//#define TRACE

#ifdef TRACE
void Platform::DebugPrintf( const char* format, ... ) {
	char buffer[2000];
	va_list pArguments;
	va_start( pArguments, format );
	vsprintf( buffer, format, pArguments );
	va_end( pArguments );
	Platform::DebugDisplay( buffer );
}
#else
void Platform::DebugPrintf( const char*, ... ) {
}
#endif

static bool assertionPopUps = true;

bool Platform::ShowAssertionPopUps( bool assertionPopUps_ ) {
	bool ret = assertionPopUps;
	assertionPopUps = assertionPopUps_;
	return ret;
}

void Platform::Assert( const char* c, const char* file, int line ) {
	char buffer[2000];
	sprintf( buffer, "Assertion [%s] failed at %s %d", c, file, line );
	if ( assertionPopUps ) {
		int idButton = ::MessageBoxA( 0, buffer, "Assertion failure",
									  MB_ABORTRETRYIGNORE | MB_ICONHAND | MB_SETFOREGROUND | MB_TASKMODAL );
		if ( idButton == IDRETRY ) {
			::DebugBreak();
		} else if ( idButton == IDIGNORE ) {
			// all OK
		} else {
			abort();
		}
	} else {
		strcat( buffer, "\r\n" );
		Platform::DebugDisplay( buffer );
		::DebugBreak();
		abort();
	}
}

int Platform::Clamp( int val, int minVal, int maxVal ) {
	if ( val > maxVal ) {
		val = maxVal;
	}
	if ( val < minVal ) {
		val = minVal;
	}
	return val;
}

void Platform_Initialise( void* /*hInstance*/ ) {
	OSVERSIONINFO osv = {sizeof( OSVERSIONINFO ), 0, 0, 0, 0, TEXT( "" )};
	::GetVersionEx( &osv );
	onNT = osv.dwPlatformId == VER_PLATFORM_WIN32_NT;
	::InitializeCriticalSection( &crPlatformLock );
	//hinstPlatformRes = reinterpret_cast<HINSTANCE>(hInstance);
	// This may be called from DllMain, in which case the call to LoadLibrary
	// is bad because it can upset the DLL load order.
	ListBoxX_Register();
}

void Platform_Finalise() {
	if ( reverseArrowCursor != NULL ) {
		::DestroyCursor( reverseArrowCursor );
	}
	ListBoxX_Unregister();
	::DeleteCriticalSection( &crPlatformLock );
}


};
